
L3: Tworzenie modeli uczenia maszynowego¶
Efekty kształcenia laboratorium¶
Podczas niniejszych zajęć:
dowiesz się jak przeprowadzić analizę statystyczną zbioru danych i na co zwrócić uwagę
poznasz podstawowe pojęcia z zakresu uczenia maszynowego
poznasz i przeprowadzisz cały proces uczenia masznowego - od wczytania zbioru, poprzez jego transformacje, uczenie, aż po walidację krzyżową i dostrajanie parametrów modelu
poznasz podstawowe mechanizmy sprawdzania jakości modelu uczenia maszynowego
Analiza statystyczna zbioru treningowego¶
Przed rozpoczęciem rozwiązywania problemu przy użyciu metod uczenia maszynowego, w szczególności przed rozpoczęciem budowania modelu, konieczne jest sprawdzenie, z jakimi danymi przyszło się nam mierzyć.
Wśród podstawowych kwestii, które powinniśmy sprawdzić, są:
ile mamy cech?
które spośród nich to cechy kategoryczne, a które numeryczne?
jakie wartości przyjmują poszczególne cechy?
czy wśród danych są brakujące wartości?
czy istnieje i jak wygląda etykieta? (w szczególności - czy mierzymy się z zadaniem klasyfikacji, regresji czy klasteryzacji?)
czy dane są zbalansowane względem danej wyjściowej?
Dla małych i prostych zbiorów do nauki (tzw. toy tasks), zazwyczaj wystarczające jest ręczne przejrzenie pliku z danymi, by potrafić odpowiedzieć na w/w pytania. Niemniej przy bardziej ambitnych zadaniach, z pomocą przychodzą narzędzia automatyzujące pracę.
Przykładowy zbiór danych¶
Przeanalizujmy klasyczny zbiór danych dotyczący wina (opublikowany przez Forina, M. et al, PARVUS - An Extendible Package for Data Exploration, Classification and Correlation. Institute of Pharmaceutical and Food Analysis and Technologies, Via Brigata Salerno, 16147 Genoa, Italy, więcej informacji tutaj)
Zbiór zawiera właściwości fizykochemiczne różnych próbek wina pobranych z jednego z regionów słonecznej Italii, jednakże pochodzących od trzech różnych plantatorów. Założeniem problemu jest określenie, który z nich jest wytwórcą danej próbki.
W celu uczynienia przykładu ambitniejszym, zbiór został celowo zaszumiony - tj. usunięto losowo część wartości.
Zacznijmy od wczytania zbioru:
import pandas as pd
dataset = pd.read_csv('../docs/lab3/wine_with_nulls.csv', )
dataset.head()
| alcohol | malic_acid | ash | alcalinity_of_ash | magnesium | total_phenols | flavanoids | nonflavanoid_phenols | proanthocyanins | color_intensity | hue | od280/od315_of_diluted_wines | proline | target | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 14.23 | 1.71 | 2.43 | 15.6 | 127.0 | 2.80 | 3.06 | 0.28 | 2.29 | 5.64 | 1.04 | 3.92 | 1065.0 | 0 |
| 1 | 13.20 | 1.78 | 2.14 | 11.2 | 100.0 | 2.65 | 2.76 | 0.26 | 1.28 | 4.38 | 1.05 | 3.40 | 1050.0 | 0 |
| 2 | 13.16 | 2.36 | 2.67 | 18.6 | 101.0 | NaN | 3.24 | 0.30 | 2.81 | 5.68 | 1.03 | 3.17 | 1185.0 | 0 |
| 3 | 14.37 | 1.95 | 2.50 | 16.8 | 113.0 | 3.85 | NaN | 0.24 | 2.18 | 7.80 | 0.86 | 3.45 | 1480.0 | 0 |
| 4 | 13.24 | 2.59 | NaN | 21.0 | 118.0 | 2.80 | 2.69 | 0.39 | 1.82 | 4.32 | 1.04 | 2.93 | 735.0 | 0 |
Na pierwszy rzut oka możemy stwierdzić, że wszystkie kolumny są numeryczne, ale ich wartości różnią się dość znacząco.
Podstawowe informacje o statystykach zbioru danych możemy uzyskać przy wbudowanej w Pandas metodzie describe()
dataset.describe()
| alcohol | malic_acid | ash | alcalinity_of_ash | magnesium | total_phenols | flavanoids | nonflavanoid_phenols | proanthocyanins | color_intensity | hue | od280/od315_of_diluted_wines | proline | target | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| count | 171.000000 | 170.000000 | 170.000000 | 167.000000 | 169.000000 | 171.000000 | 167.000000 | 171.000000 | 173.000000 | 170.000000 | 175.000000 | 170.000000 | 172.000000 | 178.000000 |
| mean | 13.009357 | 2.318059 | 2.360000 | 19.404790 | 100.088757 | 2.291988 | 2.019760 | 0.365614 | 1.583295 | 5.009529 | 0.959920 | 2.615176 | 756.209302 | 0.938202 |
| std | 0.819951 | 1.108406 | 0.275004 | 3.328986 | 14.490898 | 0.626310 | 1.006122 | 0.123074 | 0.572402 | 2.292621 | 0.228525 | 0.706861 | 315.609153 | 0.775035 |
| min | 11.030000 | 0.740000 | 1.360000 | 10.600000 | 70.000000 | 0.980000 | 0.340000 | 0.130000 | 0.410000 | 1.280000 | 0.480000 | 1.270000 | 278.000000 | 0.000000 |
| 25% | 12.370000 | 1.575000 | 2.202500 | 17.150000 | 88.000000 | 1.730000 | 1.095000 | 0.270000 | 1.250000 | 3.220000 | 0.785000 | 1.970000 | 508.000000 | 0.000000 |
| 50% | 13.050000 | 1.850000 | 2.360000 | 19.400000 | 98.000000 | 2.350000 | 2.170000 | 0.340000 | 1.540000 | 4.640000 | 0.980000 | 2.780000 | 679.000000 | 1.000000 |
| 75% | 13.700000 | 3.030000 | 2.547500 | 21.500000 | 108.000000 | 2.800000 | 2.885000 | 0.445000 | 1.950000 | 6.122500 | 1.120000 | 3.177500 | 996.250000 | 2.000000 |
| max | 14.830000 | 5.800000 | 3.230000 | 30.000000 | 162.000000 | 3.880000 | 5.080000 | 0.660000 | 3.580000 | 13.000000 | 1.710000 | 4.000000 | 1680.000000 | 2.000000 |
Jednakże w celu dokładniejszej analizy zbioru, posłużymy się biblioteką Pandas Profiling.
Pandas Profiling¶
Jest to biblioteka automatycznie analizująca zbiór danych i generujaca interaktywny raport. Alternatywnie, raport można zapisać w formacie .html
Na dzień tworzenia tego zadania (2021.02.06) wersja 2.10 biblioteki zawiera błąd nie pozwalający generować raportu w środowisku Jupyter. Posłużymy się więc wersją 2.9, która działa poprawnie.
Instalacja przebiega standardowo:
pip install pandas-profiling==2.9.0
Użycie biblioteki jest niezwykle proste:
from pandas_profiling import ProfileReport
profile = ProfileReport(dataset)
profile.to_notebook_iframe()
Z raportu dowiadujemy się między innymi:
mamy 13 kolumn numerycznych (dane wejściowe), jedną kategoryczną (etykieta) - będziemy więc zajmować się klasyfikacją
klasy są całkiem nieźle zbalansowane (39%, 33%, 27%)
mamy kolumny z pustymi wartościami
możemy dokładnie przeanalizować statystyki poszczególnych cech, ich histogramy oraz wykresy zależności pomiędzy nimi
Wprowadzenie do uczenia maszynowego¶
Uczenie maszynowe (ang. machine learning) jest obecnie najpopularniejszą dziedziną sztucznej inteligencji.
Polega ono na automatycznej budowie modelu poprzez ekspozycję algorytmu na dane treningowe w procesie zwanym uczeniem.
Model uczenia maszynowego posiada zdolność rozpoznawania wzorców wykrytych w danych, dzięki czemu jest w stanie dokonywać predykcji.
Celem uczenia maszynowego jest tworzenie modeli zdolnych do generalizacji, tj. poprawnego predykowania na danych nie użytych do treningu.
W zależności od problemu który ma zostać rozwiązany, stosuje się różne metody i algorytmy, w szczególności:
jeśli celem jest przypisanie danym pewnej kategorycznej etykiety, mówimy o klasyfikacji - np. określenie czy na zdjęciu jest kot, czy pies
jesli celem jest umiejscowienie danych na pewnej ciągłej skali liczbowej, mówimy o regresji - np. prognozowanie wartości produktu
jeśli celem jest zgrupowanie podobnych sobie danych, mówimy o klasteryzacji - np. przypisanie użytkowników Twittera do grup w zależności od poruszanych tematów
Klasyfikacja i regresja są przykładami uczenia nadzorowanego, gdzie do treningu oprócz danych wejściowych opisujących problem, musimy też posiadać etykietę, tj. klasę lub wartość która ma być wyjściem modelu.
Klasteryzacja to przykład uczenia nienadzorowanego, gdzie model realizuje swoje zadanie bez dodatkowych informacji z naszej strony.

Uczenie maszynowe przy pomocy sklearn¶
Scikit-learn (aka sklearn) jest aktualnie najpopularniejszą biblioteką w Pythonie pozwalającą kompleksowo przeprowadzać proces machine learningowy, który zazwyczaj składa się z następujących elementów:
wczytanie zbioru danych
preprocessing zbioru:
transformacje cech (normalizacja, skalowanie, kodowanie, dyskretyzacja, embeddowanie, ekstrakcja cech itd)
rozwiązanie kwestii brakujących danych
rozwiązanie kwestii powtarzających się danych
rozwiązanie kwestii imbalansu klas (oversampling/undersampling)
augmentacja danych
selekcja cech/redukcja wymiarowości
uczenie modelu
dostosowywanie (fine-tuning) parametrów
Instalacja przebiega standardowo:
pip install scikit-learn
Wczytanie zbioru danych¶
Scikit-learn posiada wbudowany zestaw standardowych, benchmarkowych zbiorów danych - jak np. używany w przykładzie zbiór wine. Pozwala także w prosty sposób generować syntetyczne dane.
Więcej informacji o dostępnych w bibliotece zbiorach danych tutaj
# używamy wczytanego wcześniej zbioru `wine`
X = dataset.drop(columns=['target'])
y = dataset['target']
Trzymając się przyjętej konwencji matematycznej, zbiór danych wejściowych nazywamy X, natomiast wyjściowych - y
Preprocessing zbioru danych¶
Odpowiednie przygotowanie danych uczących jest kluczem do sukcesu każdego przedsięwzięcia machine learningu - w myśl zasady Garbage in, garbage out. Przed przystąpieniem do uczenia, zazwyczaj konieczne jest wykonanie co najmniej kilku kroków wstępnego przetwarzania danych.
Krok ten silnie uzależniony jest od samych danych - inaczej przygotowywać będziemy dane tekstowe, numeryczne, dźwiękowe czy obrazy. Dużą rolę gra tutaj także planowany do użycia algorytm - część z nich wymaga np. ustandaryzowanych danych (tj. pochodzących z rozkładu zbliżonego do normalnego, posiadających średnią w 0 i odchylenie standardowe równe 1).
Duże znaczenie dla jakości procesu ma balans klas - w przypadku danych niezbalansowanych, część algorytmów wykazuje tendencje do preferowania klasy nadreprezentowanej, przez co popełniają błędy.
Ponadto w wielu zbiorach danych występują cechy, które nie są informacyjne. Usunięcie takich cech często pozwala na poprawę jakości predykcji. Ograniczenie ilości cech ułatwia także technicznie proces uczenia - algorytm musi przetworzyc mniej danych, wiec dzieje się to szybciej. Istnieje wiele metod selekcji cech, np. bazujacych na testach statystycznych. Ich ogólna idea polega na ocenieniu cech wg. zadanej miary jakości, a następnie wyboru najbardziej wartościowych.
Alternatywnie stosuje się metody redukcji wymiarowości. W przeciwieństwie do selekcji cech, która jedynie wybiera spośród istniejących już danych pewien ich podzbiór, algorytmy redukcji wymiarowości przekształcają dane, tworząc nowe, bardziej informacyjne cechy. Najpopularniejszą metodą redukcji wymiarowości jest PCA
Hint
Preprocessingowi poświęcone zostało Laboratorium 4, gdzie w/w mechanizmy zostaną omówione dokładniej.
W naszym komfortowym przykładzie nie ma dużej potrzeby dostosowywania danych:
klasy są w miarę dobrze zbalansowane
nie ma duplikatów
nie ma danych kategorycznych, poza etykietą, więc nie ma potrzeby ich kodowania
Obsługa danych brakujących¶
W przetwarzanym zbiorze danych występują brakujące wartości. Dane w takiej postaci nie nadają się do celów uczenia maszynowego.
Istnieją dwa podejścia na rozwiązanie tej kwestii:
usunięcie rekordów zawierających brakujące wartości
uzupełnienie tych wartości
Usunięcie rekordów z brakującymi danymi¶
Jest to rozwiązanie najprostsze i potencjalnie najlepsze dla jakości klasyfikacji - usuwając “niepewne” rekordy, uczymy klasyfikator jedynie na pełnowartościowych danych.
Minusem tego rozwiązania jest jednakże zmniejszenie się zbioru danych - polecane jest jego stosowanie jedynie w przypadku, gdy mamy wystarczającą ilość danych
# sprawdźmy, czy faktycznie występują brakujące wartości
print(X.isna().any())
print(X.shape, '\n\n')
# usuńmy je
X_dropped = X.dropna(axis=0)
# sprawdźmy ponownie
print(X_dropped.isna().any())
print(X_dropped.shape)
alcohol True
malic_acid True
ash True
alcalinity_of_ash True
magnesium True
total_phenols True
flavanoids True
nonflavanoid_phenols True
proanthocyanins True
color_intensity True
hue True
od280/od315_of_diluted_wines True
proline True
dtype: bool
(178, 13)
alcohol False
malic_acid False
ash False
alcalinity_of_ash False
magnesium False
total_phenols False
flavanoids False
nonflavanoid_phenols False
proanthocyanins False
color_intensity False
hue False
od280/od315_of_diluted_wines False
proline False
dtype: bool
(98, 13)
Uzupełnianie danych brakujących¶
W przypadku gdy mamy niedostateczną ilość danych i nie możemy ich usunąć bez konsekwencji, alternatywą jest uzupełnienie danych.
W tym celu należy wybrać odpowiednią wartość. Najpopularniejsze strategie to:
wybranie odpowiedniej statystyki (średnia/mediana) w zbiorze
wybranie odpowiedniej statystyki (średnia/mediana) w danej klasie
wybranie wartości najczęstszej w zbiorze
wybranie wartości najczęstszej w danej klasie
interpolacja wartości
Przydatne metody biblioteki Pandas: [fillna](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.fillna.html#pandas.DataFrame.fillna) [interpolate](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.interpolate.html#pandas.DataFrame.interpolate)
# uzupełnimy wartości średnimi w zbiorze
X = X.fillna(value=X.mean())
X
| alcohol | malic_acid | ash | alcalinity_of_ash | magnesium | total_phenols | flavanoids | nonflavanoid_phenols | proanthocyanins | color_intensity | hue | od280/od315_of_diluted_wines | proline | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 14.23 | 1.71 | 2.43 | 15.6 | 127.0 | 2.800000 | 3.06000 | 0.28 | 2.29 | 5.64 | 1.04 | 3.92 | 1065.0 |
| 1 | 13.20 | 1.78 | 2.14 | 11.2 | 100.0 | 2.650000 | 2.76000 | 0.26 | 1.28 | 4.38 | 1.05 | 3.40 | 1050.0 |
| 2 | 13.16 | 2.36 | 2.67 | 18.6 | 101.0 | 2.291988 | 3.24000 | 0.30 | 2.81 | 5.68 | 1.03 | 3.17 | 1185.0 |
| 3 | 14.37 | 1.95 | 2.50 | 16.8 | 113.0 | 3.850000 | 2.01976 | 0.24 | 2.18 | 7.80 | 0.86 | 3.45 | 1480.0 |
| 4 | 13.24 | 2.59 | 2.36 | 21.0 | 118.0 | 2.800000 | 2.69000 | 0.39 | 1.82 | 4.32 | 1.04 | 2.93 | 735.0 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 173 | 13.71 | 5.65 | 2.45 | 20.5 | 95.0 | 1.680000 | 0.61000 | 0.52 | 1.06 | 7.70 | 0.64 | 1.74 | 740.0 |
| 174 | 13.40 | 3.91 | 2.48 | 23.0 | 102.0 | 1.800000 | 0.75000 | 0.43 | 1.41 | 7.30 | 0.70 | 1.56 | 750.0 |
| 175 | 13.27 | 4.28 | 2.26 | 20.0 | 120.0 | 1.590000 | 0.69000 | 0.43 | 1.35 | 10.20 | 0.59 | 1.56 | 835.0 |
| 176 | 13.17 | 2.59 | 2.37 | 20.0 | 120.0 | 1.650000 | 0.68000 | 0.53 | 1.46 | 9.30 | 0.60 | 1.62 | 840.0 |
| 177 | 14.13 | 4.10 | 2.36 | 24.5 | 96.0 | 2.050000 | 0.76000 | 0.56 | 1.35 | 9.20 | 0.61 | 1.60 | 560.0 |
178 rows × 13 columns
Skalowanie cech¶
Nasz dataset jest już kompletny i zawiera dane tylko i wyłącznie numeryczne, które jednakże mają skrajnie różne wartości pomiędzy cechami. Poddamy je więc standaryzacji
from sklearn.preprocessing import StandardScaler
# zdefiniujmy funkcję wyświetlajacą statystyki średniej i odchylenia standardowego
def print_dataset_stats(X):
print('Średnia cech:\n', X.mean(axis=0))
print()
print('Odchylenie standardowe cech:\n', X.std(axis=0))
print()
Important
Większość algorytmów i klas zawartych w scikit-learn implementuje interfejs fit/transform - tj. przy pomocy funkcji fit dokonywane jest dopasowywanie algorytmów do danych uczących (uczenie/ustalanie parametrów modelu), natomiast przy pomocy funkcji transform dokonywane jest przekształcenie danych.
scaler = StandardScaler().fit(X)
print("Przed standaryzacją")
print_dataset_stats(X)
X = scaler.transform(X)
print("Po standaryzacji")
print_dataset_stats(X)
Przed standaryzacją
Średnia cech:
alcohol 13.009357
malic_acid 2.318059
ash 2.360000
alcalinity_of_ash 19.404790
magnesium 100.088757
total_phenols 2.291988
flavanoids 2.019760
nonflavanoid_phenols 0.365614
proanthocyanins 1.583295
color_intensity 5.009529
hue 0.959920
od280/od315_of_diluted_wines 2.615176
proline 756.209302
dtype: float64
Odchylenie standardowe cech:
alcohol 0.803573
malic_acid 1.083068
ash 0.268717
alcalinity_of_ash 3.223883
magnesium 14.117679
total_phenols 0.613800
flavanoids 0.974356
nonflavanoid_phenols 0.120615
proanthocyanins 0.564260
color_intensity 2.240211
hue 0.226580
od280/od315_of_diluted_wines 0.690702
proline 310.213727
dtype: float64
Po standaryzacji
Średnia cech:
[-2.91402358e-15 -1.59672525e-16 -1.26615322e-15 -3.99181312e-16
1.75639777e-15 1.99590656e-16 3.99181312e-17 -2.79426919e-16
-5.98771968e-17 2.89406451e-16 -4.79017575e-16 4.79017575e-16
-1.99590656e-16]
Odchylenie standardowe cech:
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
Uczenie modelu¶
Mając odpowiednio przygotowane dane, możemy przystąpić do procesu uczenia. Pierwszym krokiem będzie podział zbioru na część treningową (na której nauczymy model) oraz testową (na której przetestujemy jego jakość). Taki podział pozwala zmierzyć zdolność modelu do generalizacji - sprawdzamy jakość na danych, które nie były użyte do nauki, których “model wcześniej nie widział”.
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=13)
print('X_train: ', X_train.shape)
print('X_test: ', X_test.shape)
print('y_train: ', y_train.shape)
print('y_test: ', y_test.shape)
X_train: (142, 13)
X_test: (36, 13)
y_train: (142,)
y_test: (36,)
Finalnie jesteśmy gotowi stworzyć klasyfikator. Dla przykładu użyjemy algorytmu SVM. Przykłady innych modeli można znaleźć tutaj
from sklearn.svm import SVC
classifier = SVC()
classifier.fit(X_train, y_train)
SVC()
Możemy go teraz użyć w celu predykcji nowych wartości:
classifier.predict(X_test[:2])
array([2, 0])
Important
Istnieje wiele modeli uczenia maszynowego. Wybór odpowiedniego zależy od problemu który rozwiązujemy, danych które posiadamy, pożądanemu rezultatowi i warunkom działania (np. pożądana szybkość uczenia i inferencji).
Do najpopularniejszych modeli nadzorowanych należą - drzewa decyzyjne (i lasy losowe), kNN, SVM, Naive Bayes, sieci neuronowe.
Niestety, dokładne wprowadzenie w fascynującą dziedzinę modelu uczenia maszynowego leży poza zakresem tego laboratorium.
Potoki¶
W powyższym przykładzie po wczytaniu danych przetworzyliśmy je przez dwa algorytmy - StandardScaler i SVM. W przypadku bardziej rozbudowanego preprocessingu, niezwykle przydatna staje się możliwość łączenia wszystkich użytych mechanizmów w jeden potok:
from sklearn.pipeline import make_pipeline
from sklearn.datasets import load_wine
X, y = load_wine(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=13)
pipeline_classifier = make_pipeline(
StandardScaler(),
SVC()
)
pipeline_classifier.fit(X_train, y_train)
Pipeline(steps=[('standardscaler', StandardScaler()), ('svc', SVC())])
pipeline_classifier.predict(X_test[:2])
array([2, 0])
Analiza jakości modelu¶
Na tym etapie posiadamy zbiór danych podzielony na część treningową i testową, oraz klasyfikator nauczony na częsci treningowej. Jak sprawdzić, jak dobrze jest on nauczony?
Do określenia jakości modelu służy szereg metryk, różnych w zależności od problemu.
W zadaniu klasyfikacji, standardowo używa się następujących miar wywodzących się z klasyfikacji binarnej:
accuracy, oznaczająca procent poprawnie oznaczonych przykładów
F1 - będąca średnią miar
precisionirecall, mierzących skłonności modelu do niepopełniania błędów
from sklearn.metrics import f1_score
from sklearn.metrics import accuracy_score
y_predicted = pipeline_classifier.predict(X_test)
print('Accuracy: ', accuracy_score(y_test, y_predicted))
print('F1: ', f1_score(y_test, y_predicted, average='macro'))
Accuracy: 0.9722222222222222
F1: 0.9696394686907022
Dokładniejsze informacje o jakości klasyfikacji możemy uzyskac np. przy pomocy metody classification_report
from sklearn.metrics import classification_report
print(classification_report(y_test, y_predicted))
precision recall f1-score support
0 1.00 1.00 1.00 12
1 0.94 1.00 0.97 15
2 1.00 0.89 0.94 9
accuracy 0.97 36
macro avg 0.98 0.96 0.97 36
weighted avg 0.97 0.97 0.97 36
W przypadku zadania regresji, standardowo używaną miarą jest błąd średniokwadratowy (ang. mean square error). Więcej informacji o różnych rodzajach metryk jakości modelu - tutaj
Walidacja krzyżowa¶
Powyżej nauczyliśmy model klasyfikować - i to z bardzo dobrą jakością!
Jednakże, nauczyliśmy nasz model na pewnym podzbiorze danych i przetestowaliśmy na innym. Nie mamy pewności, czy model sam w sobie wykazuje się dobrą jakością, czy to akurat ten specyficzny wybór podzbiorów danych daje tak dobre wyniki.
Z pomocą przychodzi tutaj walidacja krzyżowa | sprawdzanie krzyżowe (ang. cross-validation). Jest to procedura polegająca na kilkukrotnym uruchomieniu procedury uczenia modelu, za każdym razem na innym podzbiorze danych. Uśredniony wynik takich kilku modeli pozwala z większą dokładnością wyrokować o jakości samego modelu.
from sklearn.model_selection import cross_validate
X, y = load_wine(return_X_y=True)
cv_results = cross_validate(
pipeline_classifier,
X,
y,
scoring='f1_macro'
)
cv_results['test_score'].mean()
0.983504493924021
Dostosowywanie parametrów¶
Praktycznie każdy z algorytmów uczenia maszynowego posiada pewne parametry (lub hiper-parametry), które wpływają na proces uczenia. W przypadku użytego algorytmu SVM są to np. rodzaj funkcji jądra i stopień regularyzacji.
Odpowiednie dostosowanie parametrów algorytmu do danego problemu może znacząco poprawić jakość jego działania.
Sklearn posiada zaimplementowanych kilka strategii poszukiwania najlepszych parametrów. Użyjemy tutaj najprostszego podejścia losowego:
from sklearn.model_selection import RandomizedSearchCV
X, y = load_wine(return_X_y=True)
param_distributions = {'svc__kernel': ['linear', 'poly', 'rbf', 'sigmoid'],
'svc__C': [0.1, 0.5, 1, 2, 4]}
# now create a searchCV object and fit it to the data
search = RandomizedSearchCV(estimator=pipeline_classifier,
n_iter=5,
param_distributions=param_distributions)
search.fit(X, y)
print(search.best_params_)
{'svc__kernel': 'rbf', 'svc__C': 1}
Mechanizm wykonuje ilosc powtórzeń zadaną przez argument n_iter i losuje za każdym razem wartości parametrów spośród podanych. Dla każdego z nich wykonuje cross-walidację. Możemy też przeanalizować dokładne wyniki tego procesu:
search.cv_results_
{'mean_fit_time': array([0.00217414, 0.00164518, 0.0015872 , 0.0020442 , 0.00156484]),
'std_fit_time': array([0.0007496 , 0.00054601, 0.00029603, 0.00012748, 0.0007512 ]),
'mean_score_time': array([0.00083561, 0.00057225, 0.00043321, 0.00090113, 0.00045886]),
'std_score_time': array([2.16152204e-04, 1.17527872e-04, 3.75289323e-05, 1.96044951e-04,
1.70303951e-04]),
'param_svc__kernel': masked_array(data=['rbf', 'sigmoid', 'poly', 'rbf', 'linear'],
mask=[False, False, False, False, False],
fill_value='?',
dtype=object),
'param_svc__C': masked_array(data=[1, 2, 4, 0.1, 2],
mask=[False, False, False, False, False],
fill_value='?',
dtype=object),
'params': [{'svc__kernel': 'rbf', 'svc__C': 1},
{'svc__kernel': 'sigmoid', 'svc__C': 2},
{'svc__kernel': 'poly', 'svc__C': 4},
{'svc__kernel': 'rbf', 'svc__C': 0.1},
{'svc__kernel': 'linear', 'svc__C': 2}],
'split0_test_score': array([1. , 0.91666667, 0.88888889, 0.97222222, 0.94444444]),
'split1_test_score': array([0.97222222, 0.97222222, 0.97222222, 0.97222222, 0.97222222]),
'split2_test_score': array([0.94444444, 1. , 1. , 0.94444444, 0.97222222]),
'split3_test_score': array([1. , 0.94285714, 1. , 0.94285714, 0.97142857]),
'split4_test_score': array([1. , 0.97142857, 0.94285714, 1. , 0.97142857]),
'mean_test_score': array([0.98333333, 0.96063492, 0.96079365, 0.96634921, 0.96634921]),
'std_test_score': array([0.02222222, 0.02845922, 0.04170588, 0.02113318, 0.01095813]),
'rank_test_score': array([1, 5, 4, 2, 2], dtype=int32)}
Parametry modelu sprawdzić możemy przy pomocy metody get_params() lub w dokumentacji
pipeline_classifier.get_params()
{'memory': None,
'steps': [('standardscaler', StandardScaler()), ('svc', SVC())],
'verbose': False,
'standardscaler': StandardScaler(),
'svc': SVC(),
'standardscaler__copy': True,
'standardscaler__with_mean': True,
'standardscaler__with_std': True,
'svc__C': 1.0,
'svc__break_ties': False,
'svc__cache_size': 200,
'svc__class_weight': None,
'svc__coef0': 0.0,
'svc__decision_function_shape': 'ovr',
'svc__degree': 3,
'svc__gamma': 'scale',
'svc__kernel': 'rbf',
'svc__max_iter': -1,
'svc__probability': False,
'svc__random_state': None,
'svc__shrinking': True,
'svc__tol': 0.001,
'svc__verbose': False}
See also
Istnieje szeroki wachlarz metod do selekcji parametrów modelu, poza tymi zaimplementowanymi w sklearnie. Warto przypatrzeć się Hyperopt, HpBandSter lub Optuna.
Ciekawym rozwiązaniem jest też AutoML, automatycznie dobierajacy model i parametry do zadanego problemu - np. rozwiązanie H2O AutoML